(ns net.marigolda.so-start
  (:use [net.marigolda.ci-computs]
        [net.marigolda.gui-utils])
  (:require [clojure.contrib.json :as j]
            [clojure.java.io :as io])
  (:import (java.util.zip GZIPInputStream)))

(def *so-key* "-CllGrOEmUi1ZXfG7JlCng")
(def *so-api-url* "http://api.stackoverflow.com/")
(def *so-api-version* "1.0")
(def *default-page-size* 100)

(defn format-filter-map
  "Contrusts '&key-1=value-1&...&key-n=value-n' for given map {:key-1 value-1, ..., :key-n value-n}"
  [arguments-map]
  (loop [res ""
         ks (keys arguments-map)]
         (if (empty? ks)
           res
           (recur (str res "&" (name (first ks)) "=" ((first ks) arguments-map))
                  (rest ks)))))

(defn format-filter-map-2
  "Contrusts '&key-1=value-1&...&key-n=value-n' for given map {:key-1 value-1, ..., :key-n value-n}"
  [arguments-map]
  (reduce #(str %1 "&" (name (key %2)) "=" (val %2)) "" arguments-map))

(defn read-so-api 
  "Takes method-route string of desired api method and map of optional arguments and retuns clojure representation of returned json collection.
   For example, for getting all users awarded with bagde with id 5 we need method /badges/{id} so for this
   example we would call (read-so-api \"badges/5\") where we replaced argument {id} from method route with desired value 5."
  ([method-route]
  (read-so-api method-route {} {}))
  ([method-route params-map]
  (read-so-api method-route params-map {}))
  ([method-route params-map pagination]
  (j/read-json (io/reader (GZIPInputStream. (io/input-stream (str *so-api-url* *so-api-version* method-route "?key=" *so-key* (format-filter-map-2 params-map) (format-filter-map-2 pagination))))))))
  
(def so-clojure-tags
  (map :name (:tags (read-so-api "/tags" {:filter "clojure"}))))

(defrecord Pagination [pagesize page])

(defn fetch-questions-with-answers 
  "Fetch all questions with it's answers for given filter tag. Note: maximum page size is 100 so we must loop it."
  [filter-tag]
  (loop [page 1
         total -1
         qs []]
    (cond (= -1 total) 
            (let [r (read-so-api "/questions" {:tagged filter-tag :answers "true"} (Pagination. *default-page-size* page))]
              (recur page (:total r) (:questions r)))
          (pos? (- total (* page *default-page-size*))) (recur (inc page) total (concat qs (:questions (read-so-api "/questions" {:tagged filter-tag :answers "true"} (Pagination. *default-page-size* (inc page))))))
          :else
          {:total total :questions qs})))

(defrecord User [user-id dname emailh nof-qs nof-qs-view nof-qs-up nof-qs-down nof-qs-fav nof-as nof-as-view nof-as-up nof-as-down nof-as-accept])
(defrecord Score [nof-qs nof-qs-view nof-qs-up nof-qs-down nof-qs-fav nof-as nof-as-view nof-as-up nof-as-down nof-as-accept])

;;begging for refactoring... what is sinon. for apply on record?
(defn make-score [vs]
  (Score. (nth vs 0) (nth vs 1) (nth vs 2) (nth vs 3) (nth vs 4) (nth vs 5) (nth vs 6) (nth vs 7) (nth vs 8) (nth vs 9)))  

(defn get-score
  "Makes score record for given post based on it's type (question or answer)"
  [post]
  (if-not (contains? post :answer_id)
    (make-score (concat (concat [1] (map post [:view_count :up_vote_count :down_vote_count :favorite_count])) [0 0 0 0 0]))
    (make-score (concat (concat [0 0 0 0 0 1] (map post [:view_count :up_vote_count :down_vote_count])) (if (:accepted post) [1] [0])))))

(defn make-user
  "Makes user record with data from given post"
  [post]
  (User. (:user_id (:owner post)) (:display_name (:owner post)) (:email_hash (:owner post)) 0 0 0 0 0 0 0 0 0 0))

(defn register-post
  "Updates user's score with given post data"
  [users post]
  (loop [user-id (keyword (str (:user_id (:owner post))))
         us users
         ps post]
    (if-not (contains? us user-id)
      (recur user-id (assoc us user-id (make-user post)) ps)
      (update-in us [user-id] #(merge-with + % (get-score ps))))))

(defn calc-user-scores
  "Calculate user scores for given posts"
  [posts]
  (let [users (reduce register-post {} posts)]
    (reduce register-post users (mapcat :answers posts))))

(defn my-div 
  "If devider is 0 result is 0"
  [x y]
  (if (zero? y)
    0 
    (/ x y)))

(defn calc-user-stats [user]
  (merge user {:avg-q-up (my-div (:nof-qs-up user) (:nof-qs user))
               :avg-q-down (my-div (:nof-qs-down user) (:nof-qs user))
               :avg-a-up (my-div (:nof-as-up user) (:nof-as user))
               :avg-a-down (my-div (:nof-as-down user) (:nof-as user))
               :avg-q-view (my-div (:nof-qs-view user) (:nof-qs user))
               :avg-a-view (my-div (:nof-as-view user) (:nof-as user))
               :rel-q-fav (my-div (:nof-qs-fav user) (:nof-qs user))
               :rel-a-accept (* 100 (my-div (:nof-as-accept user) (:nof-as user)))}))

(defn update-users-stats [u]
  (reduce #(merge %1 {(key %2) (calc-user-stats ((key %2) u))}) {} u ))


;; renderers
(defn start-ci []
  (def so-data (fetch-questions-with-answers "clojure"))
  (str "Retrieved \"clojure\" posts. <br/> Total number of \"clojure\" questions: <div class=\"bignum\">" (:total so-data) "</div>"))

(defn process-users []
  (def users (update-users-stats (calc-user-scores (:questions so-data))))
  (str "Users processed. <br/> Total number of users connected with \"clojure\" posts (Q/A): <div class=\"bignum\">" (count users) "</div>"
    "<br/><h4>User selection</h4>"
    "Choose: <div id=\"users-list\"/> or <input id=\"another-random\" type=\"submit\" value=\"Feel Lucky!\"/>"))

(defn get-random-user []
  ((rand-nth (keys users)) users))

(defn json-users []
 (j/json-str {:results (merge (map #(zipmap [:id :name :qs :as] (map (fn [k] (get (val %) k)) [:user-id :dname :nof-qs :nof-as])) users))}))

(defn json-users-autocomplete [v]
  (j/json-str {:results (doall (name-autocomplete-filter v (map #(zipmap [:id :name :qs :as] (map (fn [k] (get (val %) k)) [:user-id :dname :nof-qs :nof-as])) users)))}))


(defn get-user-details [id]
  (let [user ((keyword id) users)] 
    user))

(defn get-similar-user [id]
  ((get-similar-user-id ((keyword id) users) users) users))